package com.rrd.hive.udf.brickhouse.udf.timeseries;

import java.util.Map;

import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.StandardListObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.StandardMapObjectInspector;
import org.apache.log4j.Logger;

/**
 * Normalize a Vector
 */
@Description(name = "vector_normalize", value = " Normalize a Vector")
public class VectorNormalizeUDF extends GenericUDF {
    private static final Logger LOG = Logger
        .getLogger(VectorNormalizeUDF.class);
    private ListObjectInspector listInspector;
    private MapObjectInspector mapInspector;
    private PrimitiveObjectInspector valueInspector;

    private StandardListObjectInspector retListInspector;
    private StandardMapObjectInspector retMapInspector;

    public Object evaluateList(Object listObj) {
        Object retList = this.retListInspector.create(0);
        double tot = 0.0;
        for (int i = 0; i < this.listInspector.getListLength(listObj); ++i) {
            Object listVal = this.listInspector.getListElement(listObj, i);
            double listDbl = NumericUtil.getNumericValue(this.valueInspector,
                listVal);
            tot += listDbl;
        }
        for (int i = 0; i < this.listInspector.getListLength(listObj); ++i) {
            Object listVal = this.listInspector.getListElement(listObj, i);
            double listDbl = NumericUtil.getNumericValue(this.valueInspector,
                listVal);
            this.retListInspector.set(retList, i,
                NumericUtil.castToPrimitiveNumeric(listDbl / tot,
                    ((PrimitiveObjectInspector) this.retListInspector
                        .getListElementObjectInspector())
                            .getPrimitiveCategory()));
        }

        return retList;
    }

    public Object evaluateMap(Object uninspMapObj) {
        Object retMap = this.retMapInspector.create();
        Map map = this.mapInspector.getMap(uninspMapObj);
        double tot = 0.0;
        for (Object mapKey : map.keySet()) {
            Object mapValObj = map.get(mapKey);
            double mapValDbl = NumericUtil.getNumericValue(this.valueInspector,
                mapValObj);
            tot += mapValDbl;
        }
        for (Object mapKey : map.keySet()) {
            Object mapValObj = map.get(mapKey);
            double mapValDbl = NumericUtil.getNumericValue(this.valueInspector,
                mapValObj);
            double newVal = mapValDbl / tot;

            Object stdKey = ObjectInspectorUtils.copyToStandardJavaObject(
                mapKey, this.mapInspector.getMapKeyObjectInspector());
            Object stdVal = NumericUtil.castToPrimitiveNumeric(newVal,
                ((PrimitiveObjectInspector) this.retMapInspector
                    .getMapValueObjectInspector()).getPrimitiveCategory());
            this.retMapInspector.put(retMap, stdKey, stdVal);

        }
        return retMap;
    }

    @Override
    public Object evaluate(DeferredObject[] arg0) throws HiveException {
        if (this.listInspector != null) {
            return this.evaluateList(arg0[0].get());
        } else {
            return this.evaluateMap(arg0[0].get());
        }
    }

    @Override
    public String getDisplayString(String[] arg0) {
        return "vector_normalize";
    }

    private void usage(String message) throws UDFArgumentException {
        VectorNormalizeUDF.LOG
            .error("vector_normalize: Normalize a vector : " + message);
        throw new UDFArgumentException(
            "vector_normalize: Normalize a vector : " + message);
    }

    @Override
    public ObjectInspector initialize(ObjectInspector[] arg0)
            throws UDFArgumentException {
        if (arg0.length != 1) {
            this.usage("Must have one argument.");
        }

        if (arg0[0].getCategory() == Category.MAP) {
            this.mapInspector = (MapObjectInspector) arg0[0];

            if (this.mapInspector.getMapKeyObjectInspector()
                .getCategory() != Category.PRIMITIVE) {
                this.usage("Vector map key must be a primitive");
            }

            if (this.mapInspector.getMapValueObjectInspector()
                .getCategory() != Category.PRIMITIVE) {
                this.usage("Vector map value must be a primitive");
            }

            this.valueInspector = (PrimitiveObjectInspector) this.mapInspector
                .getMapValueObjectInspector();
        } else if (arg0[0].getCategory() == Category.LIST) {
            this.listInspector = (ListObjectInspector) arg0[0];

            if (this.listInspector.getListElementObjectInspector()
                .getCategory() != Category.PRIMITIVE) {
                this.usage("Vector array value must be a primitive");
            }

            this.valueInspector = (PrimitiveObjectInspector) this.listInspector
                .getListElementObjectInspector();
        } else {
            this.usage("First argument must be an array or map");
        }

        if (!NumericUtil
            .isNumericCategory(this.valueInspector.getPrimitiveCategory())) {
            this.usage(" Vector values must be numeric");
        }

        if (this.listInspector != null) {
            this.retListInspector = ObjectInspectorFactory
                .getStandardListObjectInspector(ObjectInspectorUtils
                    .getStandardObjectInspector(this.valueInspector));
            return this.retListInspector;
        } else {
            this.retMapInspector = ObjectInspectorFactory
                .getStandardMapObjectInspector(
                    ObjectInspectorUtils.getStandardObjectInspector(
                        this.mapInspector.getMapKeyObjectInspector(),
                        ObjectInspectorUtils.ObjectInspectorCopyOption.JAVA),
                    ObjectInspectorUtils.getStandardObjectInspector(
                        this.valueInspector,
                        ObjectInspectorUtils.ObjectInspectorCopyOption.JAVA));
            return this.retMapInspector;
        }
    }
}
